package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.dto.ScrollResultDTO;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.Follow;
import com.hmdp.entity.User;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IFollowService;
import com.hmdp.service.IUserService;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Array;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hmdp.utils.RedisConstants.BLOG_LIKED_KEY;
import static com.hmdp.utils.RedisConstants.FEED_KEY;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Autowired
    private IUserService userService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private IFollowService followService;

    /**
     * 根据博客id查找相关信息
     *
     * @param id
     * @return
     */
    @Override
    public Result queryBlogById(Long id) {
        Blog blog = this.getById(id);
        if (blog == null) {
            return Result.fail("博客信息暂无");
        }
        this.queryAndSetBlogUser(blog);
        this.isBlogLikedAndSet(blog);
        return Result.ok(blog);
    }

    /**
     * 判断当前用户是否给博客点赞，同时进行是否点赞的赋值
     *
     * @param blog
     */
    private void isBlogLikedAndSet(Blog blog) {
        // 用户未登录，无需设置是否点赞
        UserDTO user = UserHolder.getUser();
        if (user == null) {
            // 用户没登录，什么都不用做
            return;
        }
        // 1、获取当前用户的id
        Long userId = user.getId();
        // 2、到redis判断是否记录了用户id
        String key = BLOG_LIKED_KEY + blog.getId();
        Boolean isLiked = stringRedisTemplate.opsForZSet().score(key, userId.toString()) == null ? false : true;
        blog.setIsLike(isLiked);
    }

    /**
     * 按点赞数倒序查找博客，分页查询
     *
     * @param current
     * @return
     */
    @Override
    public Result queryHotBlog(Integer current) {
        // 根据用户查询
        Page<Blog> page = this.query()
                .orderByDesc("liked")
                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        // 查询用户
        records.forEach(blog -> {
            this.queryAndSetBlogUser(blog);
            this.isBlogLikedAndSet(blog);
        });
        return Result.ok(records);
    }

    /**
     * 用户给博客点赞，我认为可以用LUA脚本处理redis记录点赞用户，防止刷赞，
     * 但是lua执行肯定会影响性能的
     *
     * @param id
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result likeBlog(Long id) {
        // 1、获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 2、判断当前用户是否已经点赞
        String key = BLOG_LIKED_KEY + id;
        // 用zscore获取分数是否为空替代set的sismember
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        // 3、有分数，点过赞了，取消点赞
        if (score != null) {
            // 3.1、修改数据库，点赞数减一
            boolean isSuccess = this.update().setSql("liked = liked - 1").eq("id", id).update();
            // 3.2、数据库修改成功，去掉redis中记录的用户id
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().remove(key, userId.toString());
            }
        } else {
            // 4、未点赞，进行点赞操作，原来用set记录，为了实现点赞排行榜，用具有分数的zset替代，分数为当前时间戳
            boolean isSuccess = this.update().setSql("liked = liked + 1").eq("id", id).update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
            }
        }
        return Result.ok();
    }

    /**
     * 查询点赞时间排行前五的用户
     *
     * @param id
     * @return
     */
    @Override
    public Result likesBlog(Long id) {
        // 1、获取点赞时间排行前五的用户
        String key = BLOG_LIKED_KEY + id;
        Set<String> userSet = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if (userSet == null || userSet.isEmpty()) {
            return Result.ok();
        }
        // 2、获取userId
        List<Long> userIdList = userSet.stream().map(Long::valueOf).collect(Collectors.toList());
        List<User> userList = userService.query().in("id", userIdList).list();
        return Result.ok(BeanUtil.copyToList(userList, UserDTO.class));
    }

    /**
     * 查找博客的用户信息，设置一些额外的头像信息，昵称等
     *
     * @param blog
     */
    private void queryAndSetBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

    /**
     * 发布博客，同时推送给关注者
     *
     * @param blog
     * @return
     */
    @Override
    public Result saveBlog(Blog blog) {
        // 1、获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 2、保存探店博文
        this.save(blog);
        // 推送信息
        // 3、找到用户的所有关注者id
        List<Follow> followList = followService.list(
                new QueryWrapper<Follow>().eq("follow_user_id", user.getId()));
        // 4、用zset把博客id存进去，时间作为排名，关注者id表示key，相当于关注者的邮箱多了一个按时间排名的博客id
        for (Follow follow : followList) {
            stringRedisTemplate.opsForZSet().add(FEED_KEY + follow.getUserId(),
                    blog.getId().toString(),
                    System.currentTimeMillis());
        }
        // 返回id
        return Result.ok(blog.getId());
    }

    /**
     * 用户滚动分页关注博主的探店笔记动态
     * @param lastTime  前端传来的当前时间戳
     * @return
     */
    @Override
    public Result queryBlogOfFollow(Long lastTime,Integer offSet) {
        // 1、获取当前用户的id
        Long userId = UserHolder.getUser().getId();;
        // 2、查看用户redis邮箱保存的博客 zrevrangebyscore key lastTime 0 limit offset 2 固定每页两条？
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(FEED_KEY + userId.toString(),
                0, lastTime, offSet, 2);
        // 3、提取数据blogId，minTime（时间戳），offset
        if (typedTupleSet == null || typedTupleSet.isEmpty()){
            // 没有数据，返回
            return Result.ok();
        }
        long minTime = 0;
        // 博客id的集合
        List<Long> idList = new ArrayList<>();
        int os = 1;
        for (ZSetOperations.TypedTuple typedTuple:typedTupleSet){
            idList.add(Long.parseLong(typedTuple.getValue().toString()));
            // zrevrangebyscore 最后一个一定是时间最早的
            long time = typedTuple.getScore().longValue();
            // 我的理解是，向下滑动的分页，当前最小时间，计算紧接着下一次滑动的偏移量
            // 向上拉取刷新，请求最新时间的分页数据
            if (time == minTime){
                os++;
            }else {
                minTime = time;
                os = 1;
            }
        }
        // 4、根据blogId查找博客，从hashset取出的是看似无序的
        // 4.1、因为是通过hash值来存放的，所以还是要处理一下，数据库order by field(id,1...)
        String idArray = StrUtil.join(",", idList);
        List<Blog> blogList = this.list(new QueryWrapper<Blog>().in("id",idList)
                .last("order by field (id," + idArray + ")"));
        // 4.2、处理点赞
        for (Blog blog:blogList){
            queryAndSetBlogUser(blog);
            isBlogLikedAndSet(blog);
        }
        // 5、返回
        ScrollResultDTO scrollResultDTO = new ScrollResultDTO();
        scrollResultDTO.setList(blogList);
        scrollResultDTO.setMinTime(minTime);
        scrollResultDTO.setOffset(os);
        return Result.ok(scrollResultDTO);
    }
}
